﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Xml;
using HtmlAgilityPack;
using MyAnimelistRecommender.DataObject;
using MyAnimelistRecommender.Helper;
using MyAnimelistRecommender.Model;

namespace MyAnimelistRecommender.DataHelper
{
    internal class RecommendationDataHelper
    {
        private static BackgroundWorker bw = new BackgroundWorker();

        public RecommendationDataHelper()
        {
            bw.WorkerSupportsCancellation = true;
        }

        public static void AddRecToBlacklist()
        {
            if (App.Model.SelectedRecommendation != null)
            {
                App.Model.BlackList.Add(App.Model.SelectedRecommendation.Name);
                CalcBestAnime(App.Model);
            }
        }

        public static void CalcBestAnime(RecommendationModel Model)
        {
            bw.WorkerSupportsCancellation = true;

            bw.DoWork += (s, e) =>
            {
                var tmpRecs = new List<Recommendation>();

                Model.AnimeCount = Model.AnimeList.Count;

                var todo = (from a in Model.AnimeList
                            where a.Recommends != null && !a.Dropped
                            select a).ToList();
                Model.AnimeProgress = todo.Count + Model.AnimeList.Where(x => x.Dropped).Count();

                foreach (Anime a in todo)
                {
                    tmpRecs.AddRange(a.Recommends);
                }

                tmpRecs = tmpRecs.Select(x => x).Where(x => !App.Model.BlackList.Contains(x.Name)).ToList();

                var res = (from i in tmpRecs
                           where !Model.AnimeList.Select(x => x.Title).Contains(i.Name) && i.Anime.IsActivated
                           group i by i.Name into g

                           select new RecommendationGroup
                           {
                               Votes = g.Sum(x => x.RecomendCount),
                               CalcedVotes = g.Sum(x => x.RecomendCount * x.Anime.Score / 10.0),
                               Sources = g.Count(),
                               Name = g.Key,
                               Uri = g.First().Uri,
                               RecNamesAndScore = g.OrderByDescending(x => x.RecomendCount).ToDictionary(x => x.Anime.Title, x => x.RecomendCount),
                               MaxRecs = g.Max(x => x.RecomendCount),
                               Image = g.First().Image,
                               Langs = g.First().Langs,
                               Aid = g.First().Aid,
                               Rank = g.First().Rank,
                               MemberScore = g.First().MemberScore,
                               Genres = g.First().Genres
                           }).ToList();

                res = res.OrderByDescending(Model.SortingMode[Model.SelectedSortingMode]).ToList();

                res.AsParallel().ForAll(x => x.Place = res.IndexOf(x) + 1);
                var tempRecs = res.Where(x => x.Name.ToLower().Contains(Model.SearchText.ToLower()));
                if (tempRecs.Count() < 10)
                {
                    //   res = tempRecs.Union(res.Where(x => x.MaxRecName.ToLower().Contains(SearchText.ToLower()))).Distinct().ToList();
                    res = tempRecs.ToList();
                }
                else
                {
                    res = tempRecs.ToList();
                }

                e.Result = res;
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                Model.UpdateRecommendations((List<RecommendationGroup>)e.Result);
            };

            if (bw.IsBusy)
                bw.CancelAsync();

            if (!bw.CancellationPending)
                bw.RunWorkerAsync();
        }

        public static void LoadData(DependencyObject View, RecommendationModel Model, bool Continue = false)
        {
            Model.IsVeryBusy = true;
            Model.IsBusy = true;
            if (!Continue)
            {
                Model.AnimeList.Clear();
            }
            else
            {
                Model.AnimeList.Where(x => x.Recommends != null && x.Recommends.Count > 0).AsParallel().ForAll(x => x.IsWorking = true);
                Model.AnimeList.Where(x => x.Recommends == null || x.Recommends.Count == 0).AsParallel().ForAll(x => { x.IsWorking = false; x.Recommends = null; });
            }

            new Thread(() =>
            {
                HtmlAgilityPack.HtmlWeb web = new HtmlWeb();
                HtmlAgilityPack.HtmlDocument doc = web.Load("http://myanimelist.net/animelist/" + Model.UserName + "&status=7&order=0");

                HtmlNodeCollection hnc = doc.DocumentNode.SelectNodes("//body/div/table");

                bool nextBreak = false;
                Model.IsVeryBusy = false;
                for (int i = 3; i < hnc.Count; i++)
                {
                    //    Debug.WriteLine("_____________________________________________");
                    //Debug.WriteLine(hnc[i].InnerHtml);

                    HtmlNode a = hnc[i].SelectSingleNode("tr");
                    if (!a.InnerHtml.Contains("category_totals"))
                    {
                        //               Debug.WriteLine(a.InnerHtml);

                        HtmlNode name = a.Descendants("a").Where(tr => tr.GetAttributeValue("class", "") == "animetitle").SelectMany(tr => tr.Descendants("span")).ToArray()[0];
                        string link = a.Descendants("a").Where(tr => tr.GetAttributeValue("class", "") == "animetitle").Select(tr => tr.GetAttributeValue("href", "")).ToArray()[0];
                        string score = a.Descendants("td").Where(tr => tr.GetAttributeValue("width", "") == "45").Select(tr => tr.InnerText).ToArray()[0];

                        Anime c = new Anime();
                        c.Title = name.InnerText;
                        c.Uri = link + "/userrecs";
                        c.Dropped = nextBreak;
                        int blub;
                        if (int.TryParse(score, out blub))
                        {
                            c.Score = blub;
                        }
                        else
                        {
                            c.Score = 0;
                        }
                        View.Dispatcher.BeginInvoke((Action)delegate
                        {
                            if (!Continue)
                            {
                                Model.AnimeList.Add(c);
                            }
                            else
                            {
                                if (Model.AnimeList.Where(x => x.Uri == c.Uri).Count() > 0)
                                {
                                    var faa = Model.AnimeList.Where(x => x.Uri == c.Uri).First();
                                    faa.Dropped = c.Dropped;
                                    faa.Score = c.Score;
                                    faa.Title = c.Title;
                                }
                            }
                        });
                    }
                    else
                    {
                        if (nextBreak)
                        {
                            break;
                        }

                        if (hnc[i + 1].InnerText.Contains("Dropped"))
                        {
                            nextBreak = true;
                        }

                        i += 2;
                    }
                }
                View.Dispatcher.Invoke((Action)delegate
                {
                    CalcBestAnime(Model);
                });

                for (int i = 0; i < Model.AnimeList.Count; i++)
                {
                    Debug.WriteLine((i + 1) + ": " + Model.AnimeList[i].Title);
                }

                GroupingHelper.CreateGroups(Model.AnimeList.ToList());

                int takes = (int)Math.Round((decimal)Model.AnimeList.Count / (decimal)Model.Workers, 0);

                for (int i = 0; i < Model.Workers; i++)
                {
                    DownloadNextRecommendation(View, Model);
                }
            }).Start();
        }

        public static void LoadExtendedData(RecommendationModel Model, RecommendationGroup Recommendation)
        {
            if (Recommendation == null)
                return;

            var ey = Recommendation;
            List<string> loading = new List<string>();
            ey.Genres = loading;

            BackgroundWorker bw = new BackgroundWorker();
            List<string> tempGenres = new List<string>();
            string memberScore = "";
            string rank = "";
            bool isEnglish = false;

            bw.DoWork += delegate
            {
                try
                {
                    XmlDocument x = new XmlDocument();
                    x.Load("http://mal-api.com/anime/" + ey.Aid + "?format=xml");

                    XmlNodeList xn = x.SelectNodes("/anime/genre");

                    tempGenres.Clear();

                    foreach (XmlNode z in xn)
                        tempGenres.Add(z.InnerText);

                    if (tempGenres.Count == 0)
                        tempGenres.Add("N/A");

                    memberScore = x.SelectSingleNode("/anime/members_score").InnerText;
                    rank = x.SelectSingleNode("/anime/rank").InnerText;
                }
                catch (Exception)
                {
                    tempGenres.Clear();
                }
            };

            bw.RunWorkerCompleted += delegate
            {
                ey.Genres = tempGenres;
                ey.Rank = rank;
                ey.MemberScore = memberScore;

                var first = Model.AnimeList.Where(x => x.Recommends != null && x.Recommends.Where(y => y.Name == ey.Name).Count() > 0).First();
                int index = first.Recommends.IndexOf(first.Recommends.Where(y => y.Name == ey.Name).First());

                first.Recommends[index].Genres = tempGenres;
                first.Recommends[index].Rank = rank;
                first.Recommends[index].MemberScore = memberScore;
            };

            BackgroundWorker bw2 = new BackgroundWorker();
            bw2.DoWork += delegate
            {
                try
                {
                    isEnglish = Regex.Match(new WebClient().DownloadString(ey.Uri + "/characters"), "Characters & Voice Actors.*?English.*?Staff", RegexOptions.Singleline).Success;
                }
                catch
                {
                }
            };
            bw2.RunWorkerCompleted += delegate
            {
                ey.IsEnglish = isEnglish;

                var first = Model.AnimeList.Where(x => x.Recommends != null && x.Recommends.Where(y => y.Name == ey.Name).Count() > 0).First();
                int index = first.Recommends.IndexOf(first.Recommends.Where(y => y.Name == ey.Name).First());

                first.Recommends[index].IsEnglish = isEnglish;
            };

            bw.RunWorkerAsync();
            bw2.RunWorkerAsync();
        }

        public static void Save(RecommendationModel Model)
        {
            SaveState s = new SaveState();
            App.Model.SearchText = "";
            App.Model.SelectedAnime = null;
            s.Model = App.Model;
            Properties.Settings.Default.SaveState = s;
            Properties.Settings.Default.Save();
        }

        internal static void RemoveRecFromBlacklist(string p)
        {
            App.Model.BlackList.Remove(p);
            CalcBestAnime(App.Model);
        }

        private static void DownloadNextRecommendation(DependencyObject View, RecommendationModel Model, WebClient wc = null)
        {
            Anime Anime = null;
            lock (Model.AnimeList)
            {
                Anime = Model.AnimeList.Where(x => !x.IsWorking).FirstOrDefault();

                if (Anime == null)
                {
                    return;
                }
                if (wc == null)
                {
                    wc = new WebClient();
                    wc.Encoding = Encoding.UTF8;
                    wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
                }
                Anime.IsWorking = true;
                if (Anime.Dropped)
                {
                    DownloadNextRecommendation(View, Model, wc);
                }
                else
                {
                    wc.DownloadStringAsync(new Uri(Anime.Uri), new AsyncAnimeResult()
                    {
                        WebClient = wc,
                        Anime = Anime,
                        Model = Model,
                        View = View
                    });
                }
            }
        }

        private static void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            AsyncAnimeResult ar = e.UserState as AsyncAnimeResult;

            DownloadNextRecommendation(ar.View, ar.Model, ar.WebClient);

            ParseRecommendationResult(ar.View, ar.Model, e.Result, ar.Anime);
        }

        private static List<string> GetLangs(string name)
        {
            return new List<string>();

            /*
            // Funzt so leider nicht...
            // Sprache des Titels != Anime existiert in der Sprache
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(Properties.Resources.animetitles);

            List<string> langs = new List<string>();

            XmlNodeList xnList = doc.SelectNodes("/animetitles/anime/title");
            foreach (XmlNode xn in xnList)
            {
                if (xn.InnerText == name)
                {
                    foreach (XmlNode a in xn.ParentNode)
                    {
                        try
                        {
                            //  Dictionary<string,string> cultures = CultureInfo.GetCultures(CultureTypes.AllCultures).Select(x => x.TwoLetterISOLanguageName && x.DisplayName).ToDictionary(pair => pair.Key, pair => pair.Value);

                            string lang = CultureInfo.GetCultureInfo(a.Attributes["xml:lang"].Value).DisplayName.ToLower();
                            if (!langs.Contains(lang))
                                langs.Add(lang);
                        }
                        catch (Exception) { }
                    }

                    break;
                }
            }

            return langs;*/
        }

        private static void ParseRecommendationResult(DependencyObject View, RecommendationModel Model, string Html, Anime Anime)
        {
            HtmlAgilityPack.HtmlDocument recDoc = new HtmlDocument();
            recDoc.LoadHtml(Html);
            HtmlNodeCollection recHnc = recDoc.DocumentNode.SelectNodes("//body");

            ObservableCollection<Recommendation> lr = new ObservableCollection<Recommendation>();

            foreach (HtmlNode h in recHnc)
            {
                HtmlNode[] nodes = h.Descendants("div").Where(tr => tr.GetAttributeValue("id", "").Contains("raArea")).ToArray();
                foreach (HtmlNode p in nodes)
                {
                    string score = "0";
                    string image = "";
                    string name = p.ParentNode.ChildNodes[3].SelectSingleNode(p.ParentNode.ChildNodes[3].XPath + "/a").InnerText;
                    HtmlNode blub = p.ParentNode.ChildNodes[3].SelectSingleNode(p.ParentNode.ChildNodes[3].XPath + "/a");
                    string haha = blub.GetAttributeValue("href", "");

                    var img = p.SelectSingleNode(p.ParentNode.ParentNode.ChildNodes[1].XPath + "/div[1]/a[1]/img[1]");
                    if (img != null)
                    {
                        image = img.GetAttributeValue("src", "");
                    }

                    if (p.ParentNode.ChildNodes.Count >= 8)
                    {
                        score = p.ParentNode.ChildNodes[7].SelectSingleNode(p.ParentNode.ChildNodes[7].XPath + "/a/strong").InnerText;
                    }

                    int aid = int.Parse(haha.Replace("http://myanimelist.net/anime/", "").Split('/')[0]);

                    lr.Add(new Recommendation()
                    {
                        RecomendCount = int.Parse(score) + 1,
                        Name = name,
                        Uri = haha,
                        Anime = Anime,
                        Image = image,
                        Langs = GetLangs(name),
                        Aid = aid
                    });
                }
            }

            View.Dispatcher.Invoke((Action)delegate
            {
                Anime.Recommends = lr;
                CalcBestAnime(Model);

                if (Model.AnimeCount <= Model.AnimeProgress)
                {
                    Save(Model);
                    Model.IsBusy = false;
                }
            });
        }

        public static void FilterByGenres()
        {
            List<string> selectedGenres = App.Model.SelectedGenres.Split(',').ToList();
            for (int i = 0; i < selectedGenres.Count; i++)
                selectedGenres[i] = selectedGenres[i].Trim();
            App.Model.Recommendations.Where(x => x.Genres.Count == 0).ToList().AsParallel().ForAll(y => LoadAndFilterExtendedDataSync(y));
            Debug.Write("Alle geladen");
            App.Model.UpdateRecommendations(App.Model.Recommendations.Where(x => x.Genres.Intersect(selectedGenres).Count() > 0).ToList());
        }

        private static void LoadAndFilterExtendedDataSync(RecommendationGroup ey)
        {
            List<string> tempGenres = new List<string>();
            XmlDocument x = new XmlDocument();
            x.Load("http://mal-api.com/anime/" + ey.Aid + "?format=xml");

            XmlNodeList xn = x.SelectNodes("/anime/genre");

            tempGenres.Clear();

            foreach (XmlNode z in xn)
                tempGenres.Add(z.InnerText);

            if (tempGenres.Count == 0)
                tempGenres.Add("N/A");

            string memberScore = x.SelectSingleNode("/anime/members_score").InnerText;
            string rank = x.SelectSingleNode("/anime/rank").InnerText;

            ey.Genres = tempGenres;
            ey.Rank = rank;
            ey.MemberScore = memberScore;

            var first = App.Model.AnimeList.Where(z => z.Recommends != null && z.Recommends.Where(y => y.Name == ey.Name).Count() > 0).First();
            int index = first.Recommends.IndexOf(first.Recommends.Where(y => y.Name == ey.Name).First());

            first.Recommends[index].Genres = tempGenres;
            first.Recommends[index].Rank = rank;
            first.Recommends[index].MemberScore = memberScore;
        }
    }
}